/*
 * Copyright 2007 Pieter De Rycke
 * 
 * This file is part of JMTP.
 * 
 * JTMP is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as 
 * published by the Free Software Foundation, either version 3 of 
 * the License, or any later version.
 * 
 * JMTP is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU LesserGeneral Public 
 * License along with JMTP. If not, see <http://www.gnu.org/licenses/>.
 */

package jmtp;

import java.math.BigInteger;
import java.nio.file.Path;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import be.derycke.pieter.com.COMException;
import be.derycke.pieter.com.Guid;
import be.derycke.pieter.com.OleDate;

/**
 *
 * @author Pieter De Rycke
 */
class PortableDeviceObjectImplWin32 implements PortableDeviceObject {

	protected PortableDeviceContentImplWin32 content;
	protected PortableDevicePropertiesImplWin32 properties;
	protected PortableDeviceKeyCollectionImplWin32 keyCollection;
	protected PortableDeviceValuesImplWin32 values;

	protected String objectID;

	PortableDeviceObjectImplWin32(String objectID, 
			PortableDeviceContentImplWin32 content,
			PortableDevicePropertiesImplWin32 properties) {

		this.objectID = objectID;
		this.content = content;
		this.properties = properties;

		try {
			this.keyCollection = new PortableDeviceKeyCollectionImplWin32();
			this.values = new PortableDeviceValuesImplWin32();
		}
		catch (COMException e) {
			e.printStackTrace();
		}
	}

	/**
	 * A String property retrieval.
	 * @param key
	 * @return
	 */
	protected String retrieveStringValue(PropertyKey key) {
		try {
			keyCollection.clear();
			keyCollection.add(key);
			return properties.getValues(objectID, keyCollection).getStringValue(key);
		}
		catch(COMException e) {
			if(e.getHresult() == COMException.E_NOT_FOUND)
				return null;
			else if(e.getHresult() == COMException.E_NOT_SUPPORTED)
				throw new UnsupportedOperationException("Couldn't retrieve the specified property.");
			else {
				e.printStackTrace();
				return null;	//comexception -> The string was not set
			}
		}
	}

	protected void changeStringValue(PropertyKey key, String value) {
		try {
			values.clear();
			values.setStringValue(key, value);
			PortableDeviceValuesImplWin32 results = properties.setValues(objectID, values);
			if(results.count() > 0 
					&& results.getErrorValue(key).getHresult() != COMException.S_OK) {
				throw new UnsupportedOperationException("Couldn't change the property.");
			}
		}
		catch(COMException e) {
			e.printStackTrace();
		}
	}

	protected long retrieveLongValue(PropertyKey key) {
		try {
			keyCollection.clear();
			keyCollection.add(key);
			return properties.getValues(objectID, keyCollection).getUnsignedIntegerValue(key);
		}
		catch(COMException e) {
			if(e.getHresult() == COMException.E_NOT_FOUND)
				return -1;
			else if(e.getHresult() == COMException.E_NOT_SUPPORTED)
				throw new UnsupportedOperationException("Couldn't retrieve the specified property.");
			else {
				e.printStackTrace();
				return -1;
			}
		}
	}

	protected void changeLongValue(PropertyKey key, long value) {
		try {
			values.clear();
			values.setUnsignedIntegerValue(key, value);
			PortableDeviceValuesImplWin32 results = properties.setValues(objectID, values);
			if(results.count() > 0 
					&& results.getErrorValue(key).getHresult() != COMException.S_OK) {
				throw new UnsupportedOperationException("Couldn't change the property.");
			}
		}
		catch(COMException e) {
			e.printStackTrace();
		}
	}

	DateFormat format = new SimpleDateFormat("yyyy/mm/dd:HH:mm:SSS", Locale.ENGLISH);
	protected Date retrieveDateValue(PropertyKey key) {
		try {
			keyCollection.clear();
			keyCollection.add(key);
			return new OleDate(properties.getValues(objectID, keyCollection).getFloatValue(key));
		}
		catch(COMException e) {
			//TODO: tmp fix
			try {
				return format.parse(properties.getValues(objectID, keyCollection).getStringValue(key));
			} catch (ParseException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
				return null;
			} catch (COMException e1) {
				System.err.println(e1);
				return null;
			}
		}
	}

	protected void changeDateValue(PropertyKey key, Date value) {
		try {
			values.clear();
			values.setFloateValue(key, (float)new OleDate(value).toDouble());
			PortableDeviceValuesImplWin32 results = properties.setValues(objectID, values);
			if(results.count() > 0 
					&& results.getErrorValue(key).getHresult() != COMException.S_OK) {
				throw new UnsupportedOperationException("Couldn't change the property.");
			}
		}
		catch(COMException e) {}
	}

	protected boolean retrieveBooleanValue(PropertyKey key) {
		try {
			keyCollection.clear();
			keyCollection.add(key);
			return properties.getValues(objectID, keyCollection).getBoolValue(key);
		}
		catch(COMException e) {
			return false;
		}
	}

	protected Guid retrieveGuidValue(PropertyKey key) {
		try {
			keyCollection.clear();
			keyCollection.add(key);
			return properties.getValues(objectID, keyCollection).getGuidValue(key);
		}
		catch(COMException e) {
			return null;
		}
	}

	protected BigInteger retrieveBigIntegerValue(PropertyKey key) {
		try {
			keyCollection.clear();
			keyCollection.add(key);
			return properties.getValues(objectID, keyCollection).
					getUnsignedLargeIntegerValue(key);
		}
		catch(COMException e) {
			if(e.getHresult() == COMException.E_NOT_FOUND)
				return new BigInteger("-1");
			else if(e.getHresult() == COMException.E_NOT_SUPPORTED)
				throw new UnsupportedOperationException("Couldn't retrieve the specified property.");
			else {
				e.printStackTrace();
				return null;	//comexception -> The string was not set
			}
		}
	}

	protected void changeBigIntegerValue(PropertyKey key, BigInteger value) {
		try {
			values.clear();
			values.setUnsignedLargeIntegerValue(key, value);
			PortableDeviceValuesImplWin32 results = properties.setValues(objectID, values);
			if(results.count() > 0 
					&& results.getErrorValue(key).getHresult() != COMException.S_OK) {
				throw new UnsupportedOperationException("Couldn't change the property.");
			}
		}
		catch(COMException e) {
			e.printStackTrace();
		}
	}

	public String getID() {
		return objectID;
	}

	public String getName() {
		return retrieveStringValue(Win32WPDDefines.WPD_OBJECT_NAME);
	}

	public String getOriginalFileName() {
		return retrieveStringValue(Win32WPDDefines.WPD_OBJECT_ORIGINAL_FILE_NAME);
	}

	public boolean canDelete() {
		return retrieveBooleanValue(Win32WPDDefines.WPD_OBJECT_CAN_DELETE);
	}

	public boolean isHidden() {
		return retrieveBooleanValue(Win32WPDDefines.WPD_OBJECT_ISHIDDEN);
	}

	public boolean isSystemObject() {
		return retrieveBooleanValue(Win32WPDDefines.WPD_OBJECT_ISSYSTEM);
	}

	public Date getDateModified() {
		return retrieveDateValue(Win32WPDDefines.WPD_OBJECT_DATE_MODIFIED);
	}

	public Date getDateCreated() {
		return retrieveDateValue(Win32WPDDefines.WPD_OBJECT_DATE_CREATED);
	}

	public Date getDateAuthored() {
		return retrieveDateValue(Win32WPDDefines.WPD_OBJECT_DATE_AUTHORED);
	}

	public PortableDeviceObject getParent() {
		String parentID = retrieveStringValue(Win32WPDDefines.WPD_OBJECT_PARENT_ID);
		if(parentID != null)
			return WPDImplWin32.convertToPortableDeviceObject(parentID, content, properties);
		else
			return null;
	}

	public BigInteger getSize() {
		return retrieveBigIntegerValue(Win32WPDDefines.WPD_OBJECT_SIZE);
	}

	public String getPersistentUniqueIdentifier() {
		return retrieveStringValue(Win32WPDDefines.WPD_OBJECT_PERSISTENT_UNIQUE_ID);
	}

	public boolean isDrmProtected() {
		return retrieveBooleanValue(Win32WPDDefines.WPD_OBJECT_IS_DRM_PROTECTED);
	}

	public String getSyncID() {
		return retrieveStringValue(Win32WPDDefines.WPD_OBJECT_SYNC_ID);
	}

	//TODO only give the guides temporarily -> create enums
	public Guid getFormat() {
		return retrieveGuidValue(Win32WPDDefines.WPD_OBJECT_FORMAT);
	}
	//TODO only give the guides temporarily -> create enums
	public Guid getContentType() {
		return retrieveGuidValue(Win32WPDDefines.WPD_OBJECT_CONTENT_TYPE);
	}

	public void setSyncID(String value) {
		changeStringValue(Win32WPDDefines.WPD_OBJECT_SYNC_ID, value);
	}

	public void delete() {
		try {
			PortableDevicePropVariantCollectionImplWin32 collection = 
					new PortableDevicePropVariantCollectionImplWin32();
			collection.add(new PropVariant(this.objectID));
			this.content.delete(Win32WPDDefines.PORTABLE_DEVICE_DELETE_NO_RECURSION, collection);
		}
		catch(COMException e) {
			//TODO -> maybe throw an exception?
			e.printStackTrace();
		}
	}

	@Override
	public void copy(Path to) {
		try {
			this.content.copyFromPortableDeviceToHost(objectID, to.toString());
		}
		catch(COMException e) {
			//TODO -> maybe throw an exception?
			e.printStackTrace();
		}
	}

	@Override
	public String toString() {
		return objectID;
	}

	public boolean equals(Object o) {
		if(o instanceof PortableDeviceObjectImplWin32) {
			PortableDeviceObjectImplWin32 object = (PortableDeviceObjectImplWin32)o;
			return object.objectID.equals(this.objectID);
		}
		else
			return false;
	}


}
